//
// Created by Ryan on 2020/11/26.
//

#include "console.h"
#include "common.h"

// 文本输出缓冲区起点 0xb8000, 但是这里为什么要用static?
static uint16_v *text_memory = (uint16_v *)0xb8000;

static char hex_list[] = "0123456789ABCDEF";
static char dec_list[] = "0123456789";

// 字符: 一个字符占用两个字节, 包括字符内容和控制信息
// 当前坐标设定: 当前屏幕满屏量为25行80列 字符; 当然内存区满量为0x8000 字节
// 屏幕光标所在的xy坐标, 照样不知道为什么要用static
// cursor 光标, cursor_x: [0, 80) 单位字符, cursor_y: [0, 25) 单位行
static uint8_v cursor_x = 0;
static uint8_v cursor_y = 0;

void console_clean()
{
    // 字符的第二个字节, 黑色
    //    uint8_v attribute_byte = 0xf;
    // 这里uint16其实就是一个字符的长度, 直接控制为确定的值就行
    uint16_v blank = 0x0f20;
    //    uint16_v blank = 0x20ff;
    for (int i = 0; i < 80 * 25; ++i)
    {
        text_memory[i] = blank;
    }

    cursor_x = 0;
    cursor_y = 0;
    move_cursor();
}

static void screen_roll()
{
    uint16_v blank = 0xff20;
    //    uint16_v blank = 0x20ff;
    uint16_v delta = 0;

    if (cursor_y >= 25)
    {
        delta = 80 * (cursor_y - 24);
        for (int i = 0; i < 24 * 80; i++)
        {
            text_memory[i] = text_memory[i + delta];
        }
        for (int i = 24 * 80; i < 25 * 80; i++)
        {
            text_memory[i] = blank;
        }
        cursor_y = 24;
    }
}

// inline函数虽然就在这个c文件使用, 但是还是要在头文件声明才可用
inline void cursor_forward()
{
    switch (cursor_x)
    {
    case 79:
        cursor_x = 0;
        cursor_y++;
        break;
    default:
        cursor_x++;
    }
}

inline void cursor_back()
{
    switch (cursor_x)
    {
    case 0:
        cursor_x = 79;
        cursor_y--;
        break;
    default:
        cursor_x--;
    }
}

void console_write_char_color(char c, word_color back, word_color front)
{
    uint16_v word = (back << 12) | (front << 8) | c;

    // 屏幕输出包括两部分, 先输出字符后移动光标, 特别注意\r,\n,back键和空格键
    switch (c)
    {
    // \r在linux下是直接回到行首, 然后覆盖输出, 所以会有上一次输出内容没有被覆盖的情况出现,
    // windows下不会这样, 说明是系统决定.我决定使用linux方案
    case '\r':
        cursor_x = 0;
        break;
    case '\n':
        cursor_x = 0;
        cursor_y++;
        break;
    case 8:
        text_memory[cursor_x + cursor_y * 80] = word;
        cursor_back();

        break;
    default:
        text_memory[cursor_x + cursor_y * 80] = word;
        cursor_forward();
    }
    // 总体来看是多此一举的if, 但内容不是多此一举
    if (cursor_y > 24)
    {
        screen_roll();
    }
    move_cursor();
}

void console_write_string(char *string)
{
    // ??
    while (*string)
    {
        console_write_char_color(*string++, word_black, word_white);
    }
}

void console_write_string_word_color(char *string, word_color back, word_color front)
{
    while (*string)
    {
        console_write_char_color(*string++, back, front);
    }
}

// 屏幕输出十六进制的整型数字
void console_write_hex(uint32_v target, word_color back, word_color front)
{
    int8_v i = 0;
    // 注意就算是十六进制, 他是0我也是直接输出0而不是0x0
    if (!target)
    {
        console_write_char_color(0x30, word_black, word_white); // 0
        return;
    }

    console_write_char_color(0x30, word_black, word_white); // 0
    console_write_char_color(0x78, word_black, word_white); // x
    for (i = 7; i >= 0; i--)
    {
        if (target >> (i * 4) & 0xf)
        {
            break;
        }
    }
    for (; i >= 0; i--)
    {
        console_write_char_color(hex_list[target >> (i * 4) & 0xf], word_black, word_white);
        //        console_write_char_color('3', word_black, word_white);
    }
}

// 屏幕输出十进制的整型数字
void console_write_dec(uint32_v target, word_color back, word_color front)
{
    uint8_v tmp_stack[10] = {0};
    int8_v i2 = 0;

    if (!target)
    {
        console_write_char_color(0x30, word_black, word_white); // 0
        return;
    }

    for (int i = 0; i < 10; i++)
    {

        tmp_stack[i] = (uint8_v *)(target % 10);
        target /= 10;
        if (!target)
        {
            break;
        }
    }
    for (i2 = 9; i2 >= 0; i2--)
    {
        if (tmp_stack[i2])
        {
            break;
        }
    }
    for (; i2 >= 0; i2--)
    {
        console_write_char_color(dec_list[tmp_stack[i2]], word_black, word_white);
    }
}

void move_cursor()
{
    // 下一行讨论的是字符而不是内存byte
    // 其实考虑可以使用坐标而不是值, 但是还是应该用值, 因为值刚好可以和内存的位置挂钩
    uint16_v cursor_location = 80 * cursor_y + cursor_x;
    // 先跳到对应的位置, 再操作内容

    // 涉及内部寄存器, 教程中有讨论, 使用方案, 14和15含义不明
    write_b(0x3d4, 14);                     // 告诉VGA我们要设置光标的高字节
    write_b(0x3d4, cursor_location >> 8);   // 设定内容
    write_b(0x3d4, 15);                     // 告诉VGA我们要设置光标的低字节
    write_b(0x3d4, cursor_location & 0xff); // 设定内容,  我这里跟教程不一样
}
